/**
 * Title: jFamilyTree GXML provider
 * Description: jFamilyTree GXML graph provider
 * About: Designed and rewritten by Andreas Lundgren, based on the Treebolic application suite
 * Copyright: (c) 2001-2008
 * Terms of use: see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.provider.xml.graph.gxml;

import java.awt.Color;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import jFamilyTree.model.IEdge;
import jFamilyTree.model.MutableEdge;
import jFamilyTree.model.Settings;
import jFamilyTree.model.Utils;
import jFamilyTree.model.graph.Graph;
import jFamilyTree.model.graph.GraphEdge;
import jFamilyTree.model.graph.MutableGraph;
import jFamilyTree.model.graph.MutableGraphNode;

/**
 * GXL parser
 * 
 * @author Bernard Bou
 */
public class GxmlParser extends jFamilyTree.provider.xml.graph.AbstractParser
{
	/**
	 * Parse graph
	 * 
	 * @return graph if successful
	 */
	protected Graph parseGraph(Document thisDocument)
	{
		MutableGraph thisGraph = new MutableGraph();

		// map
		Hashtable<String, MutableGraphNode> theseGraphNodesById = new Hashtable<String, MutableGraphNode>();

		// C L A S S E S
		Map<String, Style> thisStyleMap = parseClasses(getFirstElementByTagName(thisDocument.getDocumentElement(), "graph"));

		// N O D E S
		NodeList theseNodeNodes = thisDocument.getElementsByTagName("node");
		for (int i = 0; i < theseNodeNodes.getLength(); i++)
		{
			Node thisNodeNode = theseNodeNodes.item(i);
			Element thisNodeElement = (Element) thisNodeNode;

			// get id
			String thisId = thisNodeElement.getAttribute("name");
			if (thisId == null || thisId.isEmpty())
				continue;

			// create node
			MutableGraphNode thisGraphNode = new MutableGraphNode(thisId);
			theseGraphNodesById.put(thisId, thisGraphNode);

			// add to graph
			thisGraph.add(thisGraphNode);

			// label
			Element thisLabelElement = getFirstLevel1ElementByTagName(thisNodeElement, "label");
			if (thisLabelElement != null)
			{
				String thisLabel = thisLabelElement.getTextContent();
				thisLabel = thisLabel.trim();
				thisGraphNode.setLabel(thisLabel);
			}

			// link
			Element thisRefElement = getFirstElementByTagName(thisNodeElement, "ref");
			if (thisRefElement != null)
			{
				String thisLink = thisRefElement.getAttribute("xlink:href");
				thisLink = thisLink.trim();
				thisGraphNode.setLink(thisLink);
			}

			// class style
			String thisClass = thisNodeElement.getAttribute("class");
			if (thisClass != null)
				transferStyle(thisGraphNode, thisStyleMap.get(thisClass));

			// style
			Element thisStyleElement = getFirstLevel1ElementByTagName(thisNodeElement, "style");
			if (thisStyleElement != null)
			{
				Element thisLineElement = getFirstLevel1ElementByTagName(thisStyleElement, "line");
				if (thisLineElement != null)
				{
					Color thisColor = toColor(thisLineElement.getAttribute("colour"));
					if (thisColor != null)
						thisGraphNode.setEdgeColor(thisColor);
				}

				Element thisFillElement = getFirstLevel1ElementByTagName(thisStyleElement, "fill");
				if (thisFillElement != null)
				{
					Color thisColor = toColor(thisFillElement.getAttribute("colour"));
					if (thisColor != null)
						thisGraphNode.setBackColor(thisColor);
					String thisString = thisFillElement.getAttribute("xlink:href");
					if (thisString != null)
						thisGraphNode.setImageFile(thisString);
				}
			}
		}

		// E D G E S
		NodeList theseEdgeNodes = thisDocument.getElementsByTagName("edge");
		for (int i = 0; i < theseEdgeNodes.getLength(); i++)
		{
			Node thisEdgeNode = theseEdgeNodes.item(i);
			Element thisEdgeElement = (Element) thisEdgeNode;

			// get end ids
			String thisFromId = thisEdgeElement.getAttribute("source");
			if (thisFromId == null)
				continue;
			String thisToId = thisEdgeElement.getAttribute("target");
			if (thisToId == null)
				continue;

			// get ends
			MutableGraphNode thisFromNode = theseGraphNodesById.get(thisFromId);
			if (thisFromNode == null)
				continue;
			MutableGraphNode thisToNode = theseGraphNodesById.get(thisToId);
			if (thisToNode == null)
				continue;

			// create edge
			MutableEdge thisEdge = new MutableEdge(thisFromNode, thisToNode);

			// insert edge into graph
			GraphEdge thisGraphEdge = new GraphEdge(thisFromNode, thisToNode, null);
			thisGraphEdge.setUserData(thisEdge);
			thisGraph.add(thisGraphEdge);

			// class style
			String thisClass = thisEdgeElement.getAttribute("class");
			if (thisClass != null)
				transferStyle(thisEdge, thisStyleMap.get(thisClass));

			// style
			parseLine(thisEdgeElement, thisEdge);
		}
		return thisGraph;
	}

	/**
	 * Parse settings
	 * 
	 * @return graph if successful
	 */
	protected Settings parseSettings(Document thisDocument)
	{
		Settings theseSettings = new Settings();
		theseSettings.theHasToolbarFlag = true;
		theseSettings.theHasStatusbarFlag = true;
		return theseSettings;
	}

	// S T Y L E

	/**
	 * Parse edge style
	 * 
	 * @param thisElement
	 *        DOM element to start from
	 * @param thisEdge
	 *        edge
	 */
	static private void parseLine(Element thisElement, MutableEdge thisEdge)
	{
		// style
		Element thisStyleElement = getFirstLevel1ElementByTagName(thisElement, "style");
		if (thisStyleElement != null)
		{
			// fore (line)
			Element thisLineElement = getFirstLevel1ElementByTagName(thisStyleElement, "line");
			if (thisLineElement != null)
			{
				Color thisColor = toColor(thisLineElement.getAttribute("colour"));
				if (thisColor != null)
					thisEdge.setColor(thisColor);

				String thisLineStyle = thisLineElement.getAttribute("linestyle");
				if (thisLineStyle != null)
				{
					int thisStyle = 0;
					if (thisLineStyle != null)
					{
						if (thisLineStyle.equals("solid"))
							thisStyle |= IEdge.SOLID;
						else if (thisLineStyle.equals("dashed"))
							thisStyle |= IEdge.DASH;
						else if (thisLineStyle.equals("dotted"))
							thisStyle |= IEdge.DOT;
					}
					thisEdge.setStyle(thisStyle);
				}
			}
		}
	}

	/**
	 * Parse class styles
	 * 
	 * @param thisElement
	 *        DOM element to start from
	 * @return style map
	 */
	static private Map<String, Style> parseClasses(Element thisElement)
	{
		Map<String, Style> thisStyleMap = new HashMap<String, Style>();

		// style
		Element thisStyleElement = getFirstLevel1ElementByTagName(thisElement, "style");
		if (thisStyleElement != null)
		{
			// fore (line)
			List<Element> theseLineElements = getLevel1ChildElementsByTagName(thisStyleElement, "line");
			if (theseLineElements != null)
				for (Element thisLineElement : theseLineElements)
				{
					String thisClass = thisLineElement.getAttribute("class");
					if (thisClass == null)
						continue;
					String thisTag = thisLineElement.getAttribute("tag");
					if (thisTag == null)
						continue;

					Color thisColor = toColor(thisLineElement.getAttribute("colour"));
					if (thisColor != null)
					{
						Style thisStyle = getStyle(thisStyleMap, thisClass);
						if (thisTag.equals("node"))
							thisStyle.theForeColor = thisColor;
						else if (thisTag.equals("edge"))
							thisStyle.theEdgeColor = thisColor;
					}
				}

			// back (fill)
			List<Element> theseFillElements = getLevel1ChildElementsByTagName(thisStyleElement, "fill");
			if (theseFillElements != null)
				for (Element thisFillElement : theseFillElements)
				{
					String thisClass = thisFillElement.getAttribute("class");
					if (thisClass == null)
						continue;
					String thisTag = thisFillElement.getAttribute("tag");
					if (thisTag == null)
						continue;

					Color thisColor = toColor(thisFillElement.getAttribute("colour"));
					if (thisColor != null)
					{
						Style thisStyle = getStyle(thisStyleMap, thisClass);
						if (thisTag.equals("node"))
							thisStyle.theBackColor = thisColor;
						else if (thisTag.equals("edge"))
							thisStyle.theEdgeColor = thisColor;
					}

					String thisString = thisFillElement.getAttribute("xlink:href");
					if (thisString != null)
					{
						Style thisStyle = getStyle(thisStyleMap, thisClass);
						if (thisTag.equals("node"))
							thisStyle.theNodeImage = thisString;
						else if (thisTag.equals("edge"))
							thisStyle.theEdgeImage = thisString;
					}
				}
		}
		return thisStyleMap;
	}

	/**
	 * Wrapper for style information
	 * 
	 * @author Bernard Bou
	 */
	static class Style
	{
		/**
		 * Node back color
		 */
		public Color theBackColor;

		/**
		 * Node fore color
		 */
		public Color theForeColor;

		/**
		 * Edge color
		 */
		public Color theEdgeColor;

		/**
		 * Nod image
		 */
		public String theNodeImage;

		/**
		 * Edge image
		 */
		public String theEdgeImage;

		/*
		 * (non-Javadoc)
		 * @see java.lang.Object#toString()
		 */
		public String toString()
		{
			return "nb=" + theBackColor + " nf=" + theForeColor + " ef=" + theEdgeColor + " ni=" + theNodeImage + " ei=" + theEdgeImage;
		}
	}

	/**
	 * Get style from map, allocate key and null value in map if needed
	 * 
	 * @param thisStyleMap
	 *        style map
	 * @param thisClass
	 *        class key
	 * @return style matching class in map (created if map did not previously contain key)
	 */
	static private Style getStyle(Map<String, Style> thisStyleMap, String thisClass)
	{
		Style thisStyle = thisStyleMap.get(thisClass);
		if (thisStyle == null)
		{
			thisStyle = new Style();
			thisStyleMap.put(thisClass, thisStyle);
		}
		return thisStyle;
	}

	/**
	 * Transfer class style to node
	 * 
	 * @param thisNode
	 *        node
	 * @param thisStyle
	 *        style
	 */
	static private void transferStyle(MutableGraphNode thisNode, Style thisStyle)
	{
		if (thisStyle == null)
			return;
		if (thisStyle.theBackColor != null)
			thisNode.setBackColor(thisStyle.theBackColor);
		if (thisStyle.theForeColor != null)
			thisNode.setForeColor(thisStyle.theForeColor);
		if (thisStyle.theEdgeColor != null)
			thisNode.setEdgeColor(thisStyle.theEdgeColor);
		if (thisStyle.theNodeImage != null)
			thisNode.setImageFile(thisStyle.theNodeImage);
		if (thisStyle.theEdgeImage != null)
			thisNode.setEdgeImageFile(thisStyle.theEdgeImage);
	}

	/**
	 * Transfer class style to edge
	 * 
	 * @param thisEdge
	 *        edge
	 * @param thisStyle
	 *        style
	 */
	static private void transferStyle(MutableEdge thisEdge, Style thisStyle)
	{
		if (thisStyle == null)
			return;
		if (thisStyle.theBackColor != null)
			thisEdge.setColor(thisStyle.theEdgeColor);
		if (thisStyle.theForeColor != null)
			thisEdge.setColor(thisStyle.theEdgeColor);
		if (thisStyle.theEdgeImage != null)
			thisEdge.setImageFile(thisStyle.theEdgeImage);
	}

	/**
	 * Color parsing
	 * 
	 * @param thisColorString
	 *        string to parse
	 * @return color
	 */
	static private Color toColor(String thisColorString)
	{
		if (thisColorString == null || thisColorString.isEmpty())
			return null;

		if (thisColorString.equals("black"))
			return Color.BLACK;
		else if (thisColorString.equals("white"))
			return Color.WHITE;
		else if (thisColorString.equals("gray"))
			return Color.GRAY;
		else if (thisColorString.equals("darkGray"))
			return Color.DARK_GRAY;
		else if (thisColorString.equals("lightGray"))
			return Color.LIGHT_GRAY;
		else if (thisColorString.equals("red"))
			return Color.RED;
		else if (thisColorString.equals("blue"))
			return Color.BLUE;
		else if (thisColorString.equals("green"))
			return Color.GREEN;
		else if (thisColorString.equals("cyan"))
			return Color.CYAN;
		else if (thisColorString.equals("magenta"))
			return Color.MAGENTA;
		else if (thisColorString.equals("yellow"))
			return Color.YELLOW;
		else if (thisColorString.equals("orange"))
			return Color.ORANGE;
		else if (thisColorString.equals("pink"))
			return Color.PINK;

		return Utils.stringToColor(thisColorString.substring(1));
	}
}
